其他
游戏开发指南:使用 UOS C# 云函数快速构建与部署服务端逻辑实战教学
本教程中涉及 UOS 服务包括:
云函数服务 Func Stateless (C#):用于部署抽卡服务端逻辑代码
云数据库服务 CRUD Storage:用于存储玩家信息、抽卡记录等数据
教程视频
教程学习大纲
在 UOS 官网下载示例项目工程 创建 UOS App 并启用 Func Stateless 服务 启用 CRUD Storage 数据库服务,并配置 Mongo 数据库 设置云函数的安装和配置目录,并安装第三方库 在本地模式下调试并运行项目 上传和部署云函数,切换到远程调用模式下测试云函数的调用 在 UOS 中连接并查看 CRUD Storage 数据库的存储信息 自定义创建一个新的云函数,并实现将数据存储在 CRUD Storage 数据库
教程案例工程源文件
教程操作步骤
1.1 下载项目工程文件
1.2 解压缩项目工程文件
1.3 加载项目工程
2.1 安装 UOS Launcher
2.2 创建 UOS App ,并绑定创建的 Unity 项目工程
2.3 开启 Func - Stateless 服务,并安装 Func Stateless SDK
开启 Func - Stateless 服务
安装 Func Stateless SDK
创建 Mongo 数据库
4. 配置项目工程
5. 设置云函数的安装和配置目录,并安装第三方库
6. 在本地模式下调试并运行项目
代码分析
[CloudService]
public class LoginService
{
private readonly IMongoCollection<User> _collection;
public LoginService()
{
Debug.Log("init login service");
var conn = Task.Run(async () => await MongoConnectionManager.GetConnection()).Result;
var database = conn.GetDatabase(MongoConfig.DBName);
_collection = database.GetCollection<User>(MongoConfig.CollectionName);
}
}
[CloudService]
public class ActionService
{
private readonly IMongoCollection<User> _collection;
public ActionService()
{
Debug.Log("init action service");
var conn = Task.Run(async () => await MongoConnectionManager.GetConnection()).Result;
var database = conn.GetDatabase(MongoConfig.DBName);
_collection = database.GetCollection<User>(MongoConfig.CollectionName);
}
}
在 MongoConfig 脚本中可看到配置的 Mongo 数据库的参数。
namespace CloudService
{
public static class MongoConfig
{
public const string DBName = "stateless-demo";
public const string CollectionName = "users";
}
}
7. 上传项目工程中已经创建的云函数
7.1 上传云函数
[CloudFunc]
public async Task<DrawResult> Draw(string id, int count)
{
Debug.Log("call to draw");
var filter = Builders<User>.Filter.Eq("_id", id);
var user = await _collection.Find(filter).FirstOrDefaultAsync();
if (user == null)
{
return new DrawResult
{
Ok = false,
Message = "user not found"
};
}
if (count > user.Diamonds)
{
return new DrawResult
{
Ok = false,
Message = "钻石不够抽卡",
};
}
// start to draw
var res = new List<Item>(count);
var pool = user.DrawPool;
if (pool == null || pool.Count == 0)
{
pool = Helper.NewDrawPool();
}
if (pool.Count < count)
{
var n = pool.Count;
var needN = count - n;
res.AddRange(pool);
pool = Helper.NewDrawPool();
var lastN = pool.Skip(pool.Count - needN).Take(needN).ToList();
pool.RemoveRange(pool.Count - needN, needN);
res.AddRange(lastN);
}
else
{
var lastCount = pool.Skip(pool.Count - count).Take(count).ToList();
pool.RemoveRange(pool.Count - count, count);
res.AddRange(lastCount);
if (res.Any(e => e.Type == 0))
{
pool = Helper.NewDrawPool();
}
}
//此处省略抽卡的逻辑代码,计算好的抽卡结果保存在 DrawResult 类型的变量中
return new DrawResult
{
Ok = true,
Items = res
};
}
public async void DrawCard(int count = 1)
{
ShowLoading(true);
try
{
await NetworkManager.DrawCard(count);
}
catch (Exception e)
{
Debug.Log(e.Message);
ShowLoading(false);
MessageUI.Show(e.Message);
}
}
public async Task DrawCard(int count)
{
var drawResult = await _as.Draw(_accountId, count);
if (!drawResult.Ok)
{
onError.Invoke(drawResult.Message, 3);
return;
}
var listResult = new List<Item>(drawResult.Items.Count);
listResult.AddRange(drawResult.Items.Select(it => new Item
{
Type = it.Type switch
{
0 => ItemType.Hero,
1 => ItemType.Prop,
_ => ItemType.Other
},
Name = it.Name,
Count = it.Count,
Level = it.Level
}));
onDrawCard.Invoke(listResult);
}
7.2 切换成远程调用模式
[CloudService]
public class ActionService
{
//此处省略其它代码......
[CloudFunc]
public async Task<GetItemsResult> GetHeroes(string id)
{
var json = "{" + $"\"id\":{JsonConvert.SerializeObject(id)}" + "}";
var jsonResult = await HttpClient.Call($"https://{ActionServiceq3k9FwJi5A.Domain}/release/d040d269-5083-4c53-babe-c3670e8a29b9/actionservice", "getheroes", json);
return JsonConvert.DeserializeObject<GetItemsResult>(jsonResult);
}
[CloudFunc]
public async Task<DrawResult> Draw(string id, int count)
{
var json = "{" + $"\"id\":{JsonConvert.SerializeObject(id)},\"count\":{JsonConvert.SerializeObject(count)}" + "}";
var jsonResult = await HttpClient.Call($"https://{ActionServiceq3k9FwJi5A.Domain}/release/d040d269-5083-4c53-babe-c3670e8a29b9/actionservice", "draw", json);
return JsonConvert.DeserializeObject<DrawResult>(jsonResult);
}
}
[CloudService]
public class LoginService
{
[CloudFunc]
public async Task<LoginResult> Login(string username, string password)
{
var json = "{" + $"\"username\":{JsonConvert.SerializeObject(username)},\"password\":{JsonConvert.SerializeObject(password)}" + "}";
var jsonResult = await HttpClient.Call($"https://{LoginServiceq3k9FwJi5A.Domain}/release/d040d269-5083-4c53-babe-c3670e8a29b9/loginservice", "login", json);
return JsonConvert.DeserializeObject<LoginResult>(jsonResult);
}
}
7.3 查看调用的日志信息
7.4 查看数据库的存储信息
8. 自行创建一个新的云函数,进行调用和测试
打开 NewService.cs 脚本后,脚本中有详细的使用说明的介绍,脚本中默认已经创建好了两个云函数 Echo 和 GetUserInfo 了,代码如下所示:
using System.Threading.Tasks;
using Unity.UOS.Func.Stateless.Core.Attributes;
using UnityEngine;
namespace CloudService
{
// 注意事项
// 1. 云函数类所在脚本文件的名称必须与类名相同。
// 2. 所有的类必须放置于命名空间内,且所用到的代码文件必须放到同一目录中。
// 3. 使用 [CloudService] 标记有远程调用函数的类,使用 [CloudFunc] 标记需要远程调用的函数。
// 4. 请在云函数类构造函数中初始化云函数,不要创建并调用其他带有参数的构造函数。
// 5. 切换到远程模式后云函数类只会保留带有 [CloudFunc] 的方法,其他字段将会被隐藏。
// 6. 使用 [CloudFunc] 标记的函数必须符合 public async Task<返回数据类型> 函数名称(输出参数) { 函数体 } 这样的格式。
// 7. 编写代码中只能使用 UnityEngine 命名空间下的 Debug.Log,Debug.LogWarning,Debug.LogError 函数,不能使用其他函数。
[CloudService]
public class NewService
{
public NewService()
{
// 初始化
}
[CloudFunc]
public async Task<string> Echo(string msg)
{
Debug.Log($"call echo with {msg}");
return msg;
}
[CloudFunc]
public async Task<UserInfo> GetUserInfo(string userId)
{
Debug.Log($"get information about user {userId}");
return new UserInfo
{
Name = "Jack",
Age = 18
};
}
}
public class UserInfo
{
public string Name;
public int Age;
}
}
然后我们需要创建一个调用云函数的 C# 脚本,当前脚本命名为 TestCall.cs,脚本代码如下所示:
using CloudService;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TestCall : MonoBehaviour
{
private async void Start()
{
var ns = new NewService();
var echoResult = await ns.Echo("hello world");
Debug.Log($"echoResult: {echoResult}");
var userInfo = await ns.GetUserInfo("userId");
Debug.Log($"userInfo: {userInfo.Name} - {userInfo.Age}");
}
}
接着,我们在场景中创建一个空的游戏对象,可以先命名为 TestCall,将创建好的脚本 TestCall.cs 挂载到场景中刚才创建的空游戏对象上。
先在本地调试模式下运行,查看控制台的打印输出结果,看到云函数本地已经被调用了。
9. 自建云函数后,将数据存储在 CRUD Storage数据库
9.1 创建云函数脚本
云函数 CreateAsync—— 来实现向数据库中插入一条信息 云函数 ReadAllAsync—— 来实现读取数据库的信息 云函数 UpdateAsync—— 来实现更新数据库中的信息 云函数 DeleteAsync—— 来实现从数据库中删除一条信息
[CloudFunc]//向数据库中插入一条信息
public async Task<bool> CreateAsync(Person p)
{
var client = await MongoConnectionManager.GetConnection();
var database = client.GetDatabase(DBName);
var collection = database.GetCollection<Person>(CollectionName);
await collection.InsertOneAsync(p);
return true;
}
[CloudFunc]//读取数据库的信息
public async Task<List<Person>> ReadAllAsync()
{
var client = await MongoConnectionManager.GetConnection();
var database = client.GetDatabase(DBName);
var collection = database.GetCollection<Person>(CollectionName);
var options = new FindOptions<Person, Person>
{
Projection = Builders<Person>.Projection.Exclude("_id")
};
var people = await collection.FindAsync(new BsonDocument(), options);
return await people.ToListAsync();
}
[CloudFunc]//更新数据库中的信息
public async Task<bool> UpdateAsync(string who, int newBalance)
{
var client = await MongoConnectionManager.GetConnection();
var database = client.GetDatabase(DBName);
var collection = database.GetCollection<Person>(CollectionName);
var filter = Builders<Person>.Filter.Eq("Name", who);
var update = Builders<Person>.Update.Set("Balance", newBalance);
var updateResult = await collection.UpdateOneAsync(filter, update);
return updateResult.IsAcknowledged;
}
[CloudFunc]//从数据库中删除一条信息
public async Task<bool> DeleteAsync(string who)
{
var client = await MongoConnectionManager.GetConnection();
var database = client.GetDatabase(DBName);
var collection = database.GetCollection<Person>(CollectionName);
var deleteFilter = Builders<Person>.Filter.Eq("Name", who);
var deleteResult = await collection.DeleteOneAsync(deleteFilter);
return deleteResult.IsAcknowledged;
}
9.2 创建调用云函数的脚本
// MongoManager.cs
using System.Text;
using CloudService;
using UnityEngine;
namespace ClientScript
{
public class MongoManager : MonoBehaviour
{
private string RandomString(int length)//随机一个字符串,后面随机用户的名字的时候会调用
{
const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
var sb = new StringBuilder();
for (var i = 0; i < length; i++)
{
var idx = Random.Range(0, chars.Length);
sb.Append(chars[idx]);
}
return sb.ToString();
}
// Start is called before the first frame update
private async void Start()
{
var ms = new MongoService();
// 插入
var personName1 = RandomString(6);
var personName2 = RandomString(6);
await ms.CreateAsync(new Person() { Name = personName1, Balance = 9999 });
await ms.CreateAsync(new Person() { Name = personName2, Balance = 999 });
// 查询
var allPerson = await ms.ReadAllAsync();
Debug.Log($"found {allPerson.Count} person");
foreach (var person in allPerson)
{
Debug.Log($"name: {person.Name}; balance: {person.Balance}");
}
// 修改
var updateResult = await ms.UpdateAsync(personName1, 123);
Debug.Log($"{personName1} modified result: {updateResult}");
// 删除
var deleteResult = await ms.DeleteAsync(personName2);
Debug.Log($"{personName2} delete result: {deleteResult}");
// 查询
allPerson = await ms.ReadAllAsync();
Debug.Log($"found {allPerson.Count} person");
foreach (var person in allPerson)
{
Debug.Log($"name: {person.Name}; balance: {person.Balance}");
}
}
}
}
MongoManager.cs 脚本还是挂载在之前创建好的游戏对象 TestCall 上。
9.3 本地调用测试
9.4 上传云函数
9.5 远程模式下调用测试
[CloudService]
public class MongoService
{
[CloudFunc]
public async Task<bool> CreateAsync(Person p)
{
var json = "{" + $"\"p\":{JsonConvert.SerializeObject(p)}" + "}";
var jsonResult = await HttpClient.Call($"https://{MongoServiceq3k9FwJi5A.Domain}/release/d040d269-5083-4c53-babe-c3670e8a29b9/mongoservice", "createasync", json);
return JsonConvert.DeserializeObject<bool>(jsonResult);
}
[CloudFunc]
public async Task<List<Person>> ReadAllAsync()
{
var json = "{" + $"" + "}";
var jsonResult = await HttpClient.Call($"https://{MongoServiceq3k9FwJi5A.Domain}/release/d040d269-5083-4c53-babe-c3670e8a29b9/mongoservice", "readallasync", json);
return JsonConvert.DeserializeObject<List<Person>>(jsonResult);
}
[CloudFunc]
public async Task<bool> UpdateAsync(string who, int newBalance)
{
var json = "{" + $"\"who\":{JsonConvert.SerializeObject(who)},\"newBalance\":{JsonConvert.SerializeObject(newBalance)}" + "}";
var jsonResult = await HttpClient.Call($"https://{MongoServiceq3k9FwJi5A.Domain}/release/d040d269-5083-4c53-babe-c3670e8a29b9/mongoservice", "updateasync", json);
return JsonConvert.DeserializeObject<bool>(jsonResult);
}
[CloudFunc]
public async Task<bool> DeleteAsync(string who)
{
var json = "{" + $"\"who\":{JsonConvert.SerializeObject(who)}" + "}";
var jsonResult = await HttpClient.Call($"https://{MongoServiceq3k9FwJi5A.Domain}/release/d040d269-5083-4c53-babe-c3670e8a29b9/mongoservice", "deleteasync", json);
return JsonConvert.DeserializeObject<bool>(jsonResult);
}
}